vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 19


Mit Referenzen arbeiten

In den letzten Kapiteln haben wir einige Aspekte von Perl betrachtet, die nicht unbedingt den Kern der Sprache selbst betreffen, sondern eher sekundärer Natur sind. So haben wir gelernt, wie man mit Hilfe bestimmter Funktionen der Standardbibliothek auf das Dateisystem zugreift, wie man Prozesse erzeugt, wie man Code aus Modulen importiert und mit diesen Modulen die verschiedensten Aufgaben löst oder wie man den Perl-Debugger nutzt. Heute wollen wir, da wir uns inzwischen dem Ende des Buches nähern, wieder dem Kern der Sprache zuwenden und auf die Referenzen zu sprechen kommen. Referenzen stellen eine Möglichkeit dar, indirekt auf andere Daten in Perl zu verweisen. Mit Referenzen kann man Daten oftmals besser und effizienter verwalten, als wenn man die Daten direkt manipulieren würde. In der heutigen Lektion widmen wir uns den folgenden Themen:

Was ist eine Referenz?

Eine Referenz ist eine Form von Daten in Perl, die auf andere Daten verweisen. Die Referenz selbst ist, wie eine Zahl oder ein String, ein Skalar. Man kann sie deshalb wie eine Zahl oder ein String einer Skalarvariablen zuweisen, ausgeben, etwas hinzufügen, auf wahr oder falsch testen, in einer Liste speichern oder einer Subroutine übergeben. Zusätzlich zu diesem skalartypischen Verhalten verweist oder zeigt eine Referenz auf die Position anderer Daten. Um herauszufinden, worauf die Referenz zeigt, dereferenziert man die Referenz - eine etwas hochgestochene Beschreibung dafür, dass man dem Verweis nachgeht, um zu sehen, worauf er verweist.

Falls Sie noch an weiteren terminologischen Pretiosen interessiert sind: das Objekt, auf das die Referenz zeigt, wird auch Referent genannt. Sie dereferenzieren also die Referenz, um den Referenten zu erhalten. Ich persönlich ziehe einfache Formulierungen wie »das Objekt, auf das verwiesen wird« vor.

Referenzen in Perl sind den Zeigern und Referenzen anderer Sprachen sehr ähnlich und bieten die gleichen Vorteile. Wenn Sie jedoch keine anderen Sprachen kennen, könnten Sie sich fragen: »Und wozu das Ganze? Warum sollte man sich mit einer Referenz herumschlagen, wenn man auch mit den Daten selbst arbeiten kann?« Nun, durch den indirekten Bezug auf die Daten können Sie fortschrittlichere Aufgaben mit diesen Daten erledigen - zum Beispiel große Datenmengen als Referenz an oder aus Subroutinen übergeben oder mehrdimensionale Arrays erzeugen. Außerdem eröffnen Referenzen neue fortgeschrittenere Techniken, beispielsweise die Erzeugung objektorientierter Strukturen in Perl. Im Verlauf dieser Lektion werden wir einige dieser Anwendungsmöglichkeiten besprechen.

Bevor ich jetzt zum eigentlichen Code komme, möchte ich noch einen Punkt klarstellen: Wenn ich in diesem Kapitel von Referenzen spreche, so versteht man in versierten Perl-Kreisen darunter die harten Referenzen. In Perl gibt es auch noch eine andere Form der Referenz, die sogenannte symbolische Referenz. So gibt es zwar zweifellos gute Gründe für die Verwendung von symbolischen Referenzen, doch sind harte Referenzen im allgemeinen wesentlich nützlicher, weshalb wir auch den Großteil dieses Kapitels damit füllen. Auf symbolische Referenzen werde ich dann im Vertiefungsabschnitt am Ende dieser Lektion etwas näher eingehen.

Die Grundlagen: Ein allgemeiner Überblick über die Verwendung von Referenzen

Betrachten wir erst einmal einige einfache Beispiele, wie man Referenzen erzeugt und verwendet, damit Sie einen Eindruck von der Technik bekommen. Dabei muss gesagt werden, dass es in Perl mehrere Möglichkeiten gibt, mit Referenzen zu arbeiten. Wir werden uns hier auf die leichtesten und am weitesten verbreiteten Mechanismen konzentrieren und die anderen in einem späteren Abschnitt (»Weitere Möglichkeiten zum Einsatz von Referenzen«) beleuchten.

Eine Referenz erzeugen

Beginnen wir mit etwas, womit Sie bereits unzählige Mal in diesem Buch zu tun hatten: eine einfache Skalarvariable, die einen String enthält.

$str = "Dies ist ein String.";

Dies ist eine ganz gewöhnliche Skalarvariable, die ganz gewöhnliche skalare Daten enthält. Es gibt eine Position im Speicher, die diesen String aufnimmt, an den Sie über den Variablennamen $str gelangen. Wenn Sie $str etwas anderes zuweisen, würde die Position im Speicher einen anderen Inhalt bekommen, und $str hätte einen anderen Wert. All dies ist allgemein bekannt, da Sie es bereits die ganze Zeit so gemacht haben.

Jetzt möchten wir eine Referenz auf diese Daten erzeugen. Um eine Referenz zu erzeugen, müssen Sie wissen, wo die fraglichen Daten (ein Skalar, ein Array, ein Hash oder eine Subroutine) im Speicher abgelegt sind. Um diese Speicherposition zu erhalten, verwenden Sie den Backslash-Operator (\) und den Variablennamen:

$strref = \$str;

Der Backslash-Operator ermittelt die Speicherposition der Daten, die in $str gespeichert sind, und erzeugt eine Referenz auf diese Position. Diese Referenz wird dann der Skalarvariablen $strref zugewiesen (zur Erinnerung: Referenzen sind eine Art von skalaren Daten).

Der Backslash-Operator ist dem Adreßoperator (&) von C sehr ähnlich. Beide werden verwendet, um auf die Speicherposition von Daten zuzugreifen.

Zu keiner Zeit kommt der eigentliche Inhalt von $str - der String »Dies ist ein String« ins Spiel. Die Referenz beschäftigt sich nicht mit dem Inhalt von $str, sondern nur mit seiner Position. Außerdem bleibt $str eine Skalarvariable, die einen String enthält. Die Existenz einer Referenz ändert daran nichts (siehe Abbildung 19.1).

Abbildung 19.1:  Eine Variable, ein String und eine Referenz

Dieses Beispiel erzeugt eine Referenz auf einen String. Sie können jedoch auch Referenzen auf Arrays, Hashes oder Subroutinen erzeugen - sozusagen auf alles, was eine Speicherposition in Perl hat. Sehen Sie im folgenden das Beispiel für eine Referenz auf ein Array:

@array = (1..10);
$arrayref = \@array;

Und hier ist ein Beispiel für einen Hash:

%hash = (
'rot' => '0 0 255',
'gruen' => '0 255 0',
'blau' => '255 0 0; );
$hashref = \%hash;

Wie schon bei der Referenz auf einen skalaren Wert bleiben das Array und der Hash in diesen Beispielen Arrays und Hashes, die in den Variablen @array und %hash gespeichert sind. Und die Array- und Hash-Referenzen in $arrayref und $hashref sind skalare Daten, unabhängig von den Daten, auf die sie zeigen. Das Wichtigste, was Sie sich hiervon merken sollten, ist die Tatsache, dass die Referenz selbst ein Skalar ist. Das, worauf die Referenz verweist, kann praktisch jede Art von Daten sein.

Sie können auch Referenzen auf Subroutinen erzeugen. Da dies jedoch ein eher fortgeschrittenes Thema ist und nicht so oft zur Anwendung kommt wie Referenzen auf Skalare, Arrays und Hashes, werde ich Referenzen auf Subroutinen und deren Einsatz erst im Vertiefungsabschnitt am Ende dieser Lektion beschreiben.

Referenzen ausgeben und verwenden

Sie haben inzwischen mit Hilfe des Backslash-Operators eine Referenz erzeugt und in einer Skalarvariablen gespeichert. Wie sehen diese Referenzen aus? Sie sind Skalare und können deshalb auch überall dort verwendet werden, wo Skalare erlaubt sind. Und wie bei Zahlen oder Strings ist ihr Verhalten kontextabhängig.

Wenn Sie eine Referenz als String verwenden, enthält der String die Information, auf welche Art von Daten die Referenz verweist (ein Skalar, ein Array, ein Hash und so weiter), und eine Hexadezimalzahl, die die interne Speicherposition angibt, auf die die Referenz zeigt. Wenn Sie also folgenden Code hätten:

print "$strref\n";

sähe die Ausgabe ungefähr folgendermaßen aus:

SCALAR(0x807f61c)

Die Ausgaben für $arrayref und $hashref wären entsprechend:

ARRAY(0x807f664)
HASH(0x807f645)

Wenn Sie eine Referenz in einem numerischen Kontext verwenden, erhalten Sie die gleiche Hexadezimalzahl wie bei Verwendung der Referenz in einem String-Kontext - eine Zahl, die die Speicherposition darstellt, auf die die Referenz zeigt. Die Zahlen, die sowohl in der String- als auch der Zahlendarstellungen der Referenz auftauchen, variieren in Abhängigkeit davon, wann das Skript ausgeführt wird und welcher Speicher gerade für Perl frei ist. Sie sollten sich nicht auf diese Zahlen verlassen; betrachten Sie sie lediglich als eine interne Darstellung des Ortes, auf den die Referenz zeigt.

Weitere Anwendungen von Referenzen? Sie können Referenzen Skalarvariablen zuweisen (wie in unserem Beispiel), sie als Listenelemente verwenden oder sie in Arrays oder Hashes speichern (Sie können sie jedoch nicht als Hash-Schlüssel verwenden - mehr dazu später). Eine Referenz, die als Test verwendet wird, liefert immer wahr zurück. Die mit Abstand gebräuchlichste Art, eine Referenz zu nutzen, besteht allerdings darin, sie zu dereferenzieren, um dadurch Zugriff auf die Daten zu erhalten, auf die die Referenz zeigt.

Referenzen dereferenzieren

Wenn Sie eine Referenz dereferenzieren, greifen Sie auf die Daten zu, auf die die Referenz zeigt. Sie können sich auch vorstellen, dass Sie der Referenz folgen oder auf die Position, auf die sich die Referenz bezieht, zugreifen. Genau dies bezeichnet man allgemein mit dem Begriff dereferenzieren.

Es gibt mehrere Möglichkeiten, eine Referenz zu dereferenzieren. Der einfachste Weg besteht darin, die Skalarvariable der Referenz dort einzusetzen, wo sonst ein einfacher Variablenname erwartet würde:

$originalstr = $$strref;

Zwei Dollarzeichen? Ja, denn ein einfaches Dollarzeichen markiert lediglich die Skalarvariable $strref, die Ihnen die Referenz selbst liefert. Das zweite Dollarzeichen besagt: »Gib mir das, worauf $strref zeigt.« In diesem Fall wäre das, worauf $strref verweist, der Originalstring »Dies ist ein String«. Sie können sich die zwei Dollarzeichen auch so erklären, dass Sie den Namen der Variablen, auf die Sie zugreifen wollen, durch die Referenz - $strref - ersetzen.

Um einer Array-Referenz zu folgen und Zugriff auf das Array selbst zu erhalten, gehen Sie genau gleich vor, allerdings mit einem @-Zeichen, und $arrayref steht dort, wo der Name des Arrays stehen würde:

@ersteliste = @$arrayref;

Der Inhalt von @ersteliste ist jetzt das ursprüngliche Array, das wir erzeugt haben und auf das $arrayref gezeigt hat (genau genommen ist es eine Kopie der ganzen Elemente dieses Arrays).

Sie möchten über eine Referenz auf ein Array-Element zugreifen? Kein Problem. Die Regel ist die gleiche: Setzen Sie einfach die Variable, die die Referenz enthält, dorthin, wo der Name des Array stehen müßte:

$erstes = $$arrayref[0];

Und mit der gleichen Regel erhalten Sie auch die oberste Indexzahl eines Arrays:

$index = $#$arrayref;

Bei Hashes ist das nicht anders:

%neueshash = %$hashref;    # das kopieren, worauf $hashref zeigt
$wert = $$hashref{rot}; # ermittelt den Wert für den Schlüssel "rot"
@keys = keys %$hashref; # extrahiert die Schlüssel

Referenzierte Daten ändern

Kommen wir jetzt zum schwierigen Teil: das Ändern der Daten, auf die die Referenz verweist. Angenommen Sie haben wie in den obigen Beispielen eine Referenz $strref. Nun wird aber der Wert von $str geändert:

$str = "Dies ist ein String."
$strref = \$str;
#...
$str = "Dies ist ein anderer String."

Was passiert mit der Referenz $strref? Sie bleibt weiter bestehen. Sie zeigt weiterhin auf die Speicherposition der Variablen namens $str. Wenn Sie sie dereferenzieren, erhalten Sie den neuen String:

print "$$strref\n";   # Ausgabe: " Dies ist ein anderer String."

Die Referenz selber schert sich nicht um den Inhalt des Speicherplatzes, auf den sie verweist, sondern nur um die Speicherposition. Sie können den Inhalt also beliebig ändern - die Referenz wird immer auf die gleiche Position zeigen. Jedesmal, wenn Sie die Referenz dereferenzieren, erhalten Sie das, was gerade zur Zeit an dieser Position im Speicher steht.

Beachten Sie, dass sich dies von der Zuweisung regulärer Variablen unterscheidet, denn dort wird der Inhalt einer Speicherposition an eine andere Position kopiert. Referenzen zeigen immer auf die gleiche Speicherposition, und der Inhalt kann ohne Beeinflussung der Referenz geändert werden. Nehmen Sie zum Beispiel folgende Anweisungen:

@array1 = qw(Achtung Fertig Los);
@array2 = @array1;
$arrayref = \@array1;
push @array1, "Stop";

$, = ' '; # setzt den Begrenzer für die Array-Elemente
print "@array1\n";
print "@array2\n";
print "@$arrayref\n";

Können Sie erraten, was für jede der drei print-Anweisungen ausgegeben wird? Der Inhalt von @array1 wird in der ersten Anweisung erzeugt und später in der vierten geändert, so dass die Ausgabe von @array1 folgendermaßen lautet:

Achtung Fertig Los Stop

@array2 wird in Zeile 2 der Inhalt von @array1 zugewiesen. Durch die Listenzuweisung wird das rechte Array in seine Bestandteile erweitert, und dann werden diese Bestandteile dem Array auf der linken Seite zugewiesen. So erhält @array2 eine Kopie des aktuellen Inhalts von @array1. Wird @array2 ausgegeben, erzeugt es folgende Ausgabe:

Achtung Fertig Los

Die Änderungen an @array2 haben keine Auswirkungen auf @array1. Beides sind getrennte Arrays mit voneinander unabhängigen Inhalten.

Die Referenz auf @array1 in $arrayref wird jedoch die gleiche Ausgabe liefern wie der aktuelle Inhalt von @array1, da die Referenz auf die gleiche Speicherposition zeigt wie @array1. Die Ausgabe dieser Dereferenzierung lautet daher:

Achtung Fertig Los Stop

Referenzen als Argumente und Rückgabewerte von Subroutinen

Inzwischen dürften Sie eine Vorstellung davon haben, wie Referenzen funktionieren. Es gäbe noch eine Menge zum Erzeugen und Verwenden von Referenzen zu sagen, aber lassen Sie uns hier erst einmal pausieren und uns der Praxis zuwenden. Im Kapitel 11, »Subroutinen erzeugen und verwenden«, habe ich im Zusammenhang mit den Subroutinen erwähnt, dass Listen und Subroutinen dazu neigen, unhandlich zu werden, wenn man keine Referenzen verwendet. Ich möchte nun auf dieses Thema zurückkommen und untersuchen, wie man mit Referenzen die Listenargumente und Rückgabewerte von Subroutinen wesentlich einfacher verwalten kann.

Subroutinenargumente

Wie Sie bereits wissen, verfügt Perl nur über ganz elementare Möglichkeiten zur Übergabe und zur Rückgabe von Argumente an und aus Subroutinen. Alle Listenargumente, die an eine Subroutine übergeben werden, werden in einer einzigen Liste zusammengefaßt und in @_ gespeichert. Rückgabewerte werden ebenfalls als einfacher Skalar oder als eine einfache Liste von Skalaren zurückgegeben. Die einfachen Argumente lassen sich dadurch zwar leicht bearbeiten, doch gilt dies nicht für Subroutinen, die mehrere Listen als Argumente übernehmen, da diese Listen auf ihrem Weg in die Subroutine ihre Identität verlieren. Wie ich bereits in Kapitel 11 angemerkt habe, können Sie diese Beschränkung auf vielfältige Weise umgehen. So können Sie zum Beispiel Listen in globalen Array- oder Hash-Variablen speichern (und damit die Übergabe von Argumenten überhaupt vermeiden) oder Informationen über die Listen selbst als Argument übergeben (wie die Länge), was Ihnen dann hilft, die Liste innerhalb der Subroutine selbst zu rekonstruieren.

Am geschicktesten - und oft auch am effizientesten - umgehen Sie Perls Hang, Listenargumente an Subroutinen zu einer Liste zusammenzufassen, indem Sie es überhaupt vermeiden, Listeninhalte an Subroutinen zu übergeben. Übergeben Sie statt dessen Referenzen, und dereferenzieren Sie diese Referenzen dann innerhalb der Subroutine, um an die Inhalte der Listen zu gelangen.

Betrachten wir ein Beispiel, das aus einer früheren Übung stammt: eine Subroutine, die zwei Arrays als Argumente übernimmt und eine Liste aller Elemente zurückliefert, die beiden gemeinsam sind (die Schnittmenge der beiden Arrays). Die Länge des ersten Arrays wird als erstes Argument übergeben, so dass wir die beiden Arrays innerhalb der Subroutine rekonstruieren können. Im folgenden Beispiel rufen wir dafür die Funktion splice auf (denken Sie daran, dass shift ohne Argumente innerhalb einer Subroutine @_ verschiebt):

1:  sub inter {
2: my @first = splice(@_,0,shift);
3: my @final = ();
4: my ($el, $el2);
5:
6: foreach $el (@first) {
7: foreach $el2 (@_) {
8: if (defined $el2 && $el eq $el2) {
9: push @final,$el2;
10: undef $el2;
11: last;
12: }
13: }
14: }
15: return @final;
16: }

Wir rufen diese Subroutine mit einer Längenangabe und zwei Arrays als Argumente auf:

@one = (1..10);
@two = (8..15);
@three = inter(scalar(@one),@one,@two);

Man könnte jetzt behaupten, dass dieses Beispiel gar nicht so schrecklich ist. Es sind schließlich nur zwei Arrays, und mit splice wird dafür Sorge getragen, dass die Elemente korrekt getrennt werden. Was aber, wenn Sie mehr als zwei Arrays als Argumente hätten? Da hätten Sie viel zu trennen. Und wenn dann noch eines der Arrays extrem groß wäre, müßten Sie erst einmal eine Menge Elemente kopieren, bevor Sie damit beginnen könnten, tatsächlich irgendwelche Elemente zu verarbeiten. Nicht gerade sehr effizient.

Wagen wir uns jetzt an eine Neufassung dieser Subroutine, die Referenzen verwendet. Anstatt der Subroutine die eigentlichen Arrays zu übergeben, übergeben wir Referenzen auf diese Arrays. Innerhalb der Subroutine weisen wir diese Referenzen dann Variablen zu und dereferenzieren sie, um den Inhalt zu erhalten. Unsere neue Subroutine könnte folgendermaßen aussehen:

1:  sub inter {
2: my ($first, $second) = @_;
3: my @final = ();
4: my ($el, $el2);
5:
6: foreach $el (@$first) {
7: foreach $el2 (@$second) {
8: if (defined $el2 && $el eq $el2) {
9: push @final,$el2;
10: undef $el2;
11: last;
12: }
13: }
14: }
15: return @final;
16: }

Diese Subroutine rufen wir nur mit zwei Argumenten auf: den Referenzen auf die Arrays:

@one = (1..10);
$oneref = \@one;
@two = (8..14);
$tworef = \@two;
@three = inter($oneref,$tworef);

Es gibt nur zwei Unterschiede zwischen dieser Subroutine und der vorherigen: die erste Zeile für die Argumentliste (Zeile 2) und die Referenzen auf die Listen in den beiden verschachtelten foreach-Schleifen (Zeilen 6 und 7). In der Referenzversion brauchen wir die Elemente der Argumentliste nicht mit splice zu trennen. Die Argumentliste enthält nur zwei Elemente: die zwei skalaren Referenzen. Deshalb können wir splice durch eine gewöhnliche Skalarzuweisung an zwei lokale Variablen ersetzen.

Mit diesen Referenzen kommen wir zu den foreach-Schleifen, die die einzelnen Listenelemente überprüfen. Hier arbeiten wir nicht mehr mit lokalen Arrays, sondern nur noch mit Referenzen. Um an den Inhalt der Arrays zu gelangen, dereferenzieren wir die Referenzen mit @$first und @$second und greifen auf diese Weise auf den Inhalt der Arrays zu.

Bei dieser Subroutine behält jedes Array seinen ursprünglichen Inhalt und Aufbau. Wenn Sie Referenzen auf Hashes übergeben, bleiben diese Hashes und werden nicht in Listen zusammengefaßt. Außerdem entfällt das lästige Kopieren der Listendaten von einem Ort zu einem anderen, wie das bei der Übergabe von regulären Listen erforderlich ist. Diese Vorgehensweise ist wesentlich effizienter und oftmals auch leichter durchzuführen und zu verstehen, vor allem bei Subroutinen mit komplexen Argumenten.

Referenzen aus Subroutinen zurückgeben

Das Gegenstück zum Übergeben von Listen an Subroutinen ist das Zurückgeben von Listen aus Subroutinen mittels des return-Operators. Standardmäßig liefert return entweder einen einfachen Skalar oder eine Liste zurück, wobei mehrere Listen in einer Liste zusammengefaßt werden.

Um mehrere Elemente aus einer Subroutine zurückzugeben und dabei die Integrität von Listen und Hashes zu wahren, liefern Sie einfach Referenzen auf diese Strukturen zurück. Gehen Sie dabei genauso vor wie bei der Übergabe von Listendaten an eine Subroutine:

sub foo {
my @templist;
my @temphash;
#...

my $tempref = \@templist;
my $temphashref = \@temphash;
return ($tempref, $temphashref);
}

Dies mag auf den ersten Blick wie ein Fehler erscheinen, denn die Variablen in diesem Beispiel, @templist und %temphash, sind lokale Variablen, die aufgelöst werden, nachdem die Subroutine ausgeführt worden ist. Wenn aber die Variablen ihren Gültigkeitsbereich verlieren, stellt sich die Frage, worauf die Referenzen überhaupt noch verweisen? Das Geheimnis hierbei ist, dass zwar der Variablenname verschwindet, wenn die Variable ihren Gültigkeitsbereich verliert (das heißt, wenn die Ausführung der Subroutine beendet ist), dass aber die Daten noch existieren und die Referenz auch noch weiterhin darauf zeigt. Genau genommen ist die Dereferenzierung dieser Referenz jetzt der einzige Weg, um weiterhin Zugriff auf diese Daten zu haben. Dieses Verhalten beeinflußt auch die Art und Weise, wie Perl Speicher belegt und freigibt, während Ihr Skript ausgeführt wird. Doch zu diesem Thema mehr im Abschnitt »Einige Anmerkungen zu Speicher und Speicherbereinigung«.

Weitere Möglichkeiten zum Einsatz von Referenzen

Meist erzeugt man Referenzen mit Hilfe des Backslash-Operators und dereferenziert sie, indem man die Referenz dort einsetzt, wo an sich ein Name erwartet wird. Doch gibt es noch viele weitere Wege, Referenzen zu erzeugen und nutzen, von denen Ihnen einige neue und komplexe Möglichkeiten eröffnen und anderes in besser lesbarer Form bewirken. In diesem Abschnitt möchte ich Ihnen einige dieser Wege zum Erzeugen und Nutzen von Referenzen aufzeigen und einige weitere Themen rund um Referenzen ansprechen.

Über Listenreferenzen auf Listenelemente zugreifen

Angenommen Sie haben eine Referenz auf eine Liste. Dann werden Sie diese Referenz wahrscheinlich am häufigsten dazu nutzen, Zugriff auf die einzelnen Elemente in der Liste zu erhalten - um sie auszugeben, zu sortieren, aufzuteilen und so weiter. Das könnten Sie zum einen mit der Syntax machen, die wir zu Beginn dieser Lektion gelernt und im vorigen Beispiel verwendet haben:

print "Kleinste Zahl: $$ref[0]\n";

Diese spezielle Zeile gibt das erste Element des Arrays aus, auf das die Referenz $ref zeigt. Wenn Sie das gleiche bei einem Hash machen wollten, müßten Sie die Hash- Syntax verwenden und den Hash-Namen durch die Referenz ersetzen:

print "Johns Nachname: $$ref{john}\n";

Es gibt jedoch noch einen anderen Weg, Zugriff auf die referenzierten Listen- und Hash-Elemente zu erhalten - ein Weg, der in vielen Fällen (besonders bei komplexen Datenstrukturen und objektorientierten Objekten) etwas einfacher zu lesen ist. Verwenden Sie die Referenz, den Pfeiloperator (->) und einen Array-Index, um auf die referenzierten Listenelemente zuzugreifen:

$erstes = $listref->[0];

Beachten Sie, dass in diesem Ausdruck nur ein Dollarzeichen steht. Dieser Ausdruck dereferenziert die Listenreferenz in der Variablen $listref und liefert das 0-te Element der Liste zurück. Damit entspricht dieser Ausdruck exakt dem Standardmechanismus zum Dereferenzieren:

$erstes = $$listref[0];

Diese Syntax läßt sich auch auf Hashes übertragen; verwenden Sie in diesem Fall die Hash-Referenz, den Pfeiloperator (->) und den Hash-Schlüssel in Klammern:

$wert = $hashref->{$key};

Diese Form ist identisch mit folgendem Ausdruck:

$wert = $$hashref{$key};

Verwechseln Sie den Pfeiloperator -> nicht mit dem Hash-Paar-Operator =>. Ersterer wird verwendet, um eine Referenz zu dereferenzieren, letzterer ist das gleiche wie ein Komma und wird verwendet, um die Syntax für die Initialisierung von Hash-Inhalten besser lesbar zu machen.

Wir werden auf diese Syntax noch einmal im Abschnitt »Verschachtelte Datenstrukturen mit Referenzen« zu sprechen kommen.

Referenzen mit Blöcken

Ein dritter Weg, Referenzen zu dereferenzieren, ist dem ersten Weg recht ähnlich. Anstatt jedoch den Namen der Referenzvariable an die Stelle eines regulären Variablennamens zu setzen, ersetzen Sie diesen durch einen Block (geschweifte Klammern), der bei seiner Auswertung eine Referenz zurückliefert. Angenommen Sie hätten zum Beispiel eine Referenz auf eine Liste:

$listref = \@list;

Mit normaler Dereferenzierung würden Sie auf das dritte Element in der Liste wie folgt zugreifen:

$third = $$listref[3];

Oder mit der Pfeilnotation:

$third = $listref->[3];

Oder mit einem Block:

$third = ${$listref}[3];

Um an den Inhalt oder den letzten Index einer Liste über eine Referenz zu gelangen, können Sie wie gewohnt schreiben:

@list = @$listref;
$index = $#$listref;

oder einen Block verwenden:

@list = @{$listref};
$index = $#{$listref};

Die obigen Blöcke sind nicht gerade besonders sinnvolle Beispiele in Anbetracht der Tatsache, dass sie lediglich die Variable $listref auswerten und Sie dies fast genauso leicht und mit weniger Zeichen durch eine ganz gewöhnliche Dereferenzierung erreichen könnten. Sie könnten jedoch in dem Block selbst eine Subroutine aufrufen, die dann eine Referenz zurückliefert, Sie könnten eine if-Bedingung verwenden, um zwischen Referenzen zu wählen, oder im Block einen weiteren Ausdruck aufnehmen. Es muss ja nicht immer nur eine einfache Variable sein.

Mit Hilfe der Blockdereferenzierung können Sie komplexe Dereferenzierungen für komplexe Strukturen, die diese Referenzen verwenden, ausführen. Dieses Thema werden wir noch ausführlicher im Abschnitt »Zugriff auf Elemente in verschachtelten Datenstrukturen« behandeln.

Die ref-Funktion

Angenommen Sie haben eine Referenz in einer Skalarvariablen gespeichert und möchten jetzt wissen, welcher Art die Daten sind, auf die die Referenz verweist, damit Sie nicht plötzlich versuchen, Listen zu multiplizieren oder Elemente aus einem String auszulesen. Dafür gibt es in Perl eine vordefinierte Funktion namens ref.

Die ref-Funktion übernimmte einen Skalar als Argument. Ist der Skalar keine Referenz, das heißt, ist er ein String oder eine Zahl, dann liefert ref einen Null-String zurück. Andernfalls liefert er einen String zurück, der angibt, welcher Art die referenzierten Daten sind. In Tabelle 19.1 finden Sie die möglichen Werte.

Rückgabewert

Darauf zeigt die Referenz

REF

Eine andere Referenz

SCALAR

Einen skalaren Wert

ARRAY

Ein Array

HASH

Ein Hash

CODE

Eine Subroutine

GLOB

Ein Typeglob (Typenplatzhalter)

** (Null-String)

Keine Referenz

Tabelle 19.1: Mögliche Rückgabewerte der ref-Funktion  

Im Vertiefungsabschnitt werden wir Referenzen auf Subroutinen und Typeglobs besprechen.

Die ref-Funktion wird meist dazu verwendet, den Typ einer Referenz zu ermitteln:

if (ref($ref) eq "ARRAY") {
foreach $key (@$ref) {
#...
}
} elsif (ref($ref eq "HASH") {
foreach $key (keys %$ref) {
#...
}
}

Einige Anmerkungen zu Speicher und Speicherbereinigung

Einer der Nebeneffekte beim Verwenden von Referenzen betrifft die Menge an Speicherplatz, die Perl besetzt, wenn es Ihr Skript ausführt und verschiedene Arten von Daten erzeugt. Normalerweise reserviert Perl bei der Ausführung Ihres Skripts automatisch Speicherbereiche für Ihre Daten und fordert diese Bereiche wieder zurück, wenn Sie fertig sind. Der Prozeß der Rückforderung - auch Speicherbereinigung genannt - unterscheidet Perl von vielen anderen Sprachen wie zum Beispiel C, wo Sie selbst Speicher allokieren und freigeben müssen.

Perl verwendet etwas, was man einen Referenzen zählenden Speicherbereiniger nennt. Das bedeutet, dass Perl für alle Daten verfolgt, wie viele Referenzen - einschließlich des ursprünglichen Variablennamens - auf die Daten verweisen. Wenn Sie also eine Referenz auf bestimmte Daten erzeugen, inkrementiert Perl die Referenzzählung um 1. Wenn Sie die Referenz auf etwas anderes verschieben oder eine lokale Variable, die Daten enthält, am Ende eines Blocks oder einer Subroutine verschwindet, dekrementiert Perl die Referenzzählung. Und liegt die Referenzzählung bei 0 - das heißt, es gibt keine Variablen, die sich auf diese Daten beziehen, noch irgendwelche Referenzen, die auf die Daten verweisen -, fordert Perl den Speicherbereich, der von den Daten eingenommen wurde, wieder zurück.

In der Regel läuft dies alles automatisch ab, und Sie müssen sich in Ihrem Skript um gar nichts kümmern. Es gibt jedoch in Zusammenhang mit den Referenzen einen Fall, bei dem Sie vorsichtig sein müssen: wenn es zu Kreisschlüssen durch Referenzen kommt.

Betrachten wir die folgenden zwei Referenzen:

sub silly {
my ($ref1, $ref2);
$ref1 = \$ref2;
$ref2 = \$ref1;
# .. törichte Dinge!
}

In diesem Beispiel zeigt die Referenz in $ref1 auf das, worauf auch $ref2 zeigt, und $ref2 zeigt auf das, worauf auch $ref1 zeigt. Dieses Phänomen nennt man einen Kreisschluß. Die Schwierigkeit dabei ist, dass die lokalen Variablennamen $ref1 und $ref2 zwar verschwinden, wenn die Subroutine ihre Ausführung beendet hat, die in den lokalen Variablen enthaltenen Daten aber weiterhin zumindest einmal referenziert werden, so dass der Speicher für diese Referenzen nicht zurückgefordert werden kann. Und ohne die Variablennamen oder eine zurückgelieferte Referenz auf die eine oder andere Referenz können Sie nicht einmal auf die Daten aus der Subroutine zugreifen. Die Daten liegen dann einfach so im Speicher herum. Und jedesmal, wenn die Subroutine bei Ausführung Ihres Skripts aufgerufen wird, wächst der Speicherbereich, den Sie nicht mehr zurückfordern können, an, bis Perl irgendwann den ganzen Speicher auf Ihrem System belegt hat, wenn Ihr Skript nicht vorher die Ausführung abbricht.

Kreisschlüsse durch Referenzen sind demnach schädlich. Und sollte Ihnen dieses Beispiel hier etwas dumm und zu leicht zu durchschauen erscheinen, so möchte ich Sie darauf hinweisen, dass man bei komplexen Datenstrukturen, bei denen überall Referenzen irgendwo hinzeigen, durchaus Gefahr läuft, zufällig und unbeabsichtigt eine Kreisreferenz zu erzeugen. Denken Sie also daran, alle Referenzen, die Sie in Blöcken oder Subroutinen verwenden, »aufzuräumen« (verwenden Sie undef, oder weisen Sie ihnen eine 0 oder '' zu), um sicherzustellen, dass der Speicher immer wieder zurückgefordert wird.

Verschachtelte Datenstrukturen mit Referenzen

Doch Referenzen sind nicht nur in Subroutinen nützlich. Sie dienen noch einem anderen wichtigen Zweck. Ohne sie gäbe es keine komplexen Datenstrukturen wie zum Beispiel mehrdimensionale Arrays. In diesem Abschnitt möchte ich Ihnen zeigen, wie man komplexe Datenstrukturen mit verschachtelten Arrays und Hashes auf der Basis von Referenzen und anonymen Daten aufbaut. Weiter hinten in dieser Lektion, im Abschnitt »Zugriff auf Elemente in verschachtelten Datenstrukturen«, erfahren Sie dann, wie Sie die Daten wieder aus den verschachtelten Datenstrukturen, die Sie gerade erzeugt haben, auslesen.

Was sind verschachtelte Datenstrukturen?

Normalerweise sind Listen, Arrays und Hashes in Perl flach und eindimensional und enthalten nichts außer Skalare. Wenn Sie mehrere Listen kombinieren, werden diese alle in einer Liste zusammengefaßt. Hashes sind im Grunde genommen das gleiche wie Listen, bei denen nur die Daten intern anders organisiert sind. Dadurch wird zwar das Erzeugen und Verwenden von Datensammlungen recht einfach, wenn Sie jedoch versuchen, größere oder komplexere Datensätze effizient zu repräsentieren, stoßen Sie bald an die Grenzen dieses Konzepts.

Angenommen Ihre Daten bestehen aus Informationen über Menschen: Vorname, Nachname, Alter, Größe und Namen der Kinder. Wie würden Sie diese Daten darstellen? Vor- und Nachname sind kein Problem: Sie erzeugen einen Hash mit dem Nachnamen als Schlüssel und weisen diesem Schlüssel dann die Vornamen als Werte zu. Jetzt die Größe - nun gut, Sie könnten für die Größen einen zweiten Hash erzeugen, der ebenfalls den Nachnamen als Schlüssel verwendet. Doch was machen Sie, wenn Sie die Namen der Kinder aufnehmen wollen? Vielleicht einen dritten Hash einrichten, der ebenfalls den Nachnamen als Schlüssel nutzt und dessen Werte Strings sind, die die durch Doppelpunkte getrennten Namen der Kinder enthalten und bei Bedarf wieder in die einzelnen Namen zerlegt werden? Sie sehen: Sobald die Daten komplex werden, versinkt man in einem Wust einfacher Listen, oder man erzeugt seltsame Gebilde mit Strings, weil man ansonsten keine Listen innerhalb von anderen Listen speichern kann.

Hier kommen nun die Referenzen ins Spiel. Eine Liste ist eine flache Sammlung von Skalaren - daran ändert sich auch nach Einführung der Referenzen nichts. Aber eine Referenz ist ein Skalar - und eine Referenz kann auf eine andere Liste verweisen. Und diese Liste kann selbst auch wieder Referenzen auf andere Listen enthalten. Kapiert? Mit Referenzen können Sie Listen in Listen, Arrays in Arrays und Hashes in Arrays verschachteln, aber auch Arrays als Werte für Hashes verwenden und so weiter. All diese Konstrukte - eine beliebige Kombination von Listen, Arrays und Hashes - nennen wir verschachtelte Datenstrukturen.

Anonyme Daten verwenden

Auch wenn Referenzen für das Erzeugen von verschachtelten Datenstrukturen unentbehrlich sind, sind sie nicht das einzige Hilfsmittel, das das Erstellen von verschachtelten Datenstrukturen leichter macht. Neben den Referenzen sollte man sich auch mit anonymen Daten auskennen. Der Begriff anonym bedeutet »ohne einen Namen«. Anonyme Daten beziehen sich speziell in Perl auf Daten (normalerweise Arrays oder Hashes, aber auch Subroutinen), auf die nur über eine Referenz zugegriffen werden kann - also Daten, die mit keinem Variablennamen verbundenen sind.

Wie schon im letzten Abschnitt betrachten wir auch hier vornehmlich Arrays und Hashes. Anonyme Subroutinen und die Referenzen werde ich im Vertiefungsabschnitt am Ende dieser Lektion ansprechen.

Anonymen Daten sind wir schon weiter vorne in diesem Kapitel begegnet, als wir Arrays in Subroutinen erzeugt und dann Referenzen auf diese Arrays zurückgeliefert haben. Nachdem die Subroutine abgelaufen ist, verschwindet die ursprüngliche lokale Variable, die die Daten enthält, und der einzige Weg, auf die Daten zuzugreifen, führt über eine Referenz. Diese Daten nennt man dann anonym.

Anonyme Daten sind für verschachtelte Datenstrukturen nützlich, weil Sie für die Erzeugung einer Liste von Listen dann nur einen einzigen Variablennamen für die äußere Liste benötigen (und selbst auf diesen könnte man verzichten). Sie brauchen keine einzelnen Variablen für alle Daten innerhalb der Liste, Referenzen reichen völlig aus.

Sie könnten die anonymen Daten für Ihre verschachtelten Datenstrukturen mit Hilfe lokaler Variablen von Subroutinen oder Blöcken erzeugen. In einigen Situationen, beispielsweise wenn Sie Strukturen mit Daten füllen, die Sie aus Dateien oder der Standardeingabe einlesen, ist dies sogar meist der einfachste Weg. Es gibt aber noch eine andere Möglichkeit, anonyme Daten zu erzeugen, und zwar mit Hilfe einer speziellen Perl-Syntax: eckige Klammern [] oder geschweifte Klammern {}.

Angenommen Sie wollten eine Referenz auf ein Array erzeugen. Normalerweise würden Sie dazu wie folgt vorgehen:

@array = ( 1..10 );
$arrayref = \@array;

Doch daran ist nichts anonym. Das Array wird in der Variablen @array gespeichert. Um das gleiche Array anonym zu erzeugen, müssen Sie die Liste in eckigen Klammern statt in runden Klammern initialisieren. Das Ergebnis wäre eine Referenz, die Sie dann speichern können:

$listref = [ 1..10 ];

Auf dieses Array kann über die Referenz zugegriffen werden. Sie können die Referenz genauso wie für jedes andere Array dereferenzieren. Aber Sie können nicht über einen Variablennamen auf das Array zugreifen - es handelt sich um anonyme Daten.

Mit anonymen Hashes können Sie genauso verfahren. Hier erzeugen die geschweiften Klammern eine Referenz auf einen anonymen Hash:

$hashref = {
'Taylor' => 12,
'Ashley' => 11,
'Jason' => 12.
'Brendan' => 13,
}

Beachten Sie, dass die Elemente in dem Hash weiterhin als Paare vorliegen - eine Kombination aus Schlüssel und Werten, wie bei normalen Hashes.

Die hier verwendeten eckigen und geschweiften Klammern sollten nicht mit denen verwechselt werden, die bei den Array-Indizes $array[0] und den Hash-Zugriffen $hash{key} zum Einsatz kommen. Die Zeichen sind die gleichen, so dass man sich leichter merken kann, dass eckige Klammern zu den Arrays gehören und geschweifte Klammern zu den Hashes. Aber die Funktion der Klammern ist eine völlig andere.

Array- und Hash-Klammern konstruieren ein Array oder ein Hash im Speicher und liefern dann eine Referenz auf diese Speicherposition zurück. Alles, was Sie in einem Array oder einem Hash ablegen können, können Sie auch in anonyme Array- oder Hash-Klammern setzen. Dazu gehören auch Array- und Hash-Variablen, obwohl die folgenden zwei Zeilen nicht das gleiche Ergebnis liefern:

$arrayref = \@array;
$arrayref = [ @array ];

Der Unterschied zwischen diesen zwei Zeilen ist der, dass die erste Referenz auf die eigentliche Speicherposition der Variablen @array zeigt, während die zweite ein neues Array anlegt, in dem alle Elemente von @array kopiert werden, und dann eine Referenz auf diese neue Speicherposition erzeugt. Sie könnten es auch als die Erzeugung einer Referenz auf eine Kopie von @array bezeichnen. Diesen Trick sollten Sie sich unbedingt für später merken, wenn wir Datenstrukturen in Schleifen erzeugen.

Datenstrukturen mit anonymen Daten erzeugen

Mit anonymen Daten und Referenzen ist das Erstellen von verschachtelten Datenstrukturen letztlich nur eine Frage des Zusammenfügens. In diesem Abschnitt untersuchen wir drei verschiedene Arten von verschachtelten Datenstrukturen: Arrays von Arrays, Hashes von Arrays und Hashes von Hashes.

Arrays von Arrays

Beginnen wir mit etwas einfachem: einem Array von Arrays oder auch einem mehrdimensionalen Array (siehe Abbildung 0.2). Sie könnten zum Beispiel ein Array von Arrays verwenden, um eine Art von zweidimensionalem Feld (wie ein Schachbrett) zu erzeugen. Dabei hat jedes Feld auf dem »Brett« eine Position irgendwo in einer Reihe, und die Reihen werden in dem größeren Array gespeichert.

Abbildung 19.2:  Ein Array von Arrays

Wenn Sie von C her an echte mehrdimensionale Arrays gewöhnt sind, sollten Sie sich vergegenwärtigen, dass die mehrdimensionalen Arrays in Perl eher Arrays von Zeigern sind und nicht im eigentlichen Sinne mehrdimensional.

Um ein Array von Arrays zu erzeugen, verwendet man für die inneren Arrays die Syntax der anonymen Arrays und für das äußere Array die reguläre Listensyntax. Das folgende Array von Arrays enthält die RGB-Werte für verschiedene Schattierungen von Grau.

@grauwerte = (
[ 0, 0, 0 ],
[ 63, 63, 63 ],
[ 127, 127, 127 ],
[ 191, 191, 191 ],
[ 255, 255, 255],
);

Hier stehen die Arrays mit den Zahlen in eckigen Klammern, wodurch Referenzen auf diese Arrays erzeugt werden. Dann erzeugt das größere Array innerhalb der runden Klammern ein einfaches Array dieser Referenzen. Hüten Sie sich vor folgendem Fehler:

@grauwerte = [
[ 0, 0, 0 ],
[ 63, 63, 63 ],
#...
];

Die eckigen Klammern um das äußere Array erzeugen eine Referenz auf ein Array und kein normales Array. Vielleicht ist es das, was Sie wollen, aber dann würden Sie das Ergebnis nicht einer Array-Variablen zuweisen. Statt dessen würden Sie die Referenz in einer Skalarvariablen unterbringen.

Hashes von Arrays

Ein Hash von Arrays ist eine verschachtelte Datenstruktur, in der ein Hash mit normalen Schlüsseln als Werte Referenzen auf Arrays erhält (siehe Abbildung 19.3). Sie könnten in einem Hash von Arrays zum Beispiel eine Liste von Personen und deren Kindern abspeichern. Die Schlüssel dieses Hash wären die Namen der Personen und die dazugehörigen Werte Listen der Namen der Kinder. Ein anderes Beispiel wäre ein Skript für ein Kino, das in einem Hash die Filme und deren Vorführungszeiten festhält.

Abbildung 19.3:  Hashes von Arrays

Um einen Hash von Arrays zu erzeugen, verwenden Sie für den äußeren Hash die Hash-Syntax, normale Strings als Schlüssel und anonyme Arrays als deren Werte (das folgende Beispiel enthält den Plan der sportlichen Aktivitäten in einem Sommerlager):

%plan = (
'Montag' => [ 'Bogenschiessen', 'Fussball', 'Tanz' ],
'Dienstag' => [ 'Korbflechten', 'Schwimmen', 'Kanufahren' ],
'Mittwoch' => [ 'Exkursion', 'Fussball', 'Tanz' ],
'Donnerstag' => [ 'Freizeit', 'Schwimmen', 'Kanufahren' ],
'Freitag' => [ 'Bogenschiessen', 'Fussball', 'Wandern' ],
);

Auch hier gilt es sorgfältig zwischen eckigen und runden Klammern zu unterscheiden. Die inneren eckigen Klammern erzeugen die anonymen Arrays. Die äußeren runden Klammern erzeugen eine Liste, die dann in einen Hash umgewandelt wird, wenn sie der Variablen %plan zugewiesen wird. Mit geschweiften Klammern um die ganze Liste würden Sie eine Referenz auf einen anonymen Hash erzeugen.

Beachten Sie, dass in einem Hash von Arrays nur die Werte Arrays sein können. Die Hash-Schlüssel müssen Strings sein. Genau genommen, geht Perl davon aus, dass es Strings sind, und wandelt gnadenlos alles andere (Zahlen und Referenzen) in Strings um. Achten Sie darauf, dass Sie bei verschachtelten Hashes für Ihre Schlüssel Strings verwenden.

Hashes von Hashes

Wie komplex hätten Sie es gerne? Hashes von Hashes ermöglichen es Ihnen, sehr komplexe Datenstrukturen zu erzeugen. In einem Hash von Hashes enthält der äußere Hash normale Schlüssel und Werte, in denen wiederum Hashes gespeichert sind (siehe Abbildung 19.4). Sie könnten dann mit speziellen Schlüsseln und »Unterschlüsseln« in den einzelnen Hashes suchen. In einem Hash von Hashes könnte man beispielsweise die Kinder einer Schulklasse und die Noten der Kinder in den einzelnen Fächern erfassen. Dabei bilden die Nachnamen der Kinder den Schlüssel, und die Werte wären untergeordnete Hashes mit Einträgen für den Vornamen, das Geburtsdatum und die Noten für jedes Fach.

Abbildung 19.4:  Hashes von Hashes

Hashes von Hashes verwenden für den inneren Teil die Syntax anonymer Hashes und für den äußeren Teil die Syntax regulärer Listen:

%leute = (
'Jones' => {
'name' => 'Alison',
'alter' => 15,
'tier' => 'dog',
},
'Smith' => {
'name' => 'Tom',
'alter' => 18,
'tier' => 'fish',
},
);

Andere Strukturen

Ich habe Ihnen in diesem Abschnitt drei einfache und häufig vorkommende Datenstrukturen vorgestellt: Arrays von Arrays, Hashes von Arrays und Hashes von Hashes. Sie können Arrays und Hashes jedoch fast beliebig mit Referenzen und anonymen Daten kombinieren, je nachdem, mit welchen Daten Sie arbeiten und wie sich die Daten am besten organisieren lassen. Sie können Ihre Daten auch noch tiefer verschachteln, als ich es hier getan habe. Denkbar wäre zum Beispiel auch ein Hash von einem Hash, in dem die Schlüssel Arrays und die Array-Elemente wiederum Hashes sind und so weiter. Es sind Ihnen hinsichtlich der Verschachtelungstiefe keine Grenzen gesetzt. Wenn es also die Situation erfordert, dann sollten Sie sich keine Beschränkungen auferlegen.

Datenstrukturen mit existierenden Daten aufbauen

Die obigen Beispiele für verschachtelte Datenstrukturen mit anonymen Daten lassen sich überall dort gut anwenden, wo Sie bereits im voraus genau wissen, welche Daten die Strukturen enthalten werden. In der Praxis sieht es jedoch so aus, dass solche komplexeren Datenstrukturen meistens aus Daten aufgebaut sind, die aus einer Datei gelesen oder über die Tastatur eingegeben wurden.

In solchen Fällen kann es passieren, dass Sie sowohl mit anonymen Daten als auch mit Referenzen auf reguläre Variablen arbeiten. So lange die Referenzen dabei an der richtigen Stelle stehen, ist es egal, welchen Mechanismus Sie zum Aufbau Ihrer Datenstruktur verwenden. Angenommen Sie haben eine Datei, die die folgende Matrix von Zahlen enthält:

3 4 2 4 2 3
5 3 2 4 5 4
7 6 3 2 8 3
3 4 7 8 3 4

Sie wollen diese Datei in ein Array von Arrays einlesen, wobei jede Reihe ein eigenes Array bildet und das äußere Array die einzelnen Reihen speichert. Sie könnten dies mit einer Schleife wie folgt lösen:

while (<>) {
chomp;
push @matrix, [ split ];
}

Diese Schleife würde jede Zeile lesen, das Neue-Zeile-Zeichen mit chomp entfernen, mit split die einzelnen Elemente voneinander trennen, mit Hilfe der eckigen Klammern ein anonymes Array dieser Elemente erzeugen und zum Schluß die Referenz auf dieses Array in das äußere Array schreiben (push).

Vielleicht denken Sie, dass dieses Beispiel besser lesbar wäre, wenn wir die Elemente zuerst in einer Liste aufsplitten und dann eine Referenz auf diese Liste speichern würden:

my @list = ();
while (<>) {
chomp;
@list = split;
push @matrix, \@list;
}

Aber dieses Beispiel hat einen großen Haken (eine Falle, in die viele Programmierer tappen, die dies zum ersten Mal versuchen). Für eine vorgegebene Eingabe wie die obige Matrix wird diese Schleife ein Array von Arrays erzeugen, das folgendermaßen aussieht:

3 4 7 8 3 4
3 4 7 8 3 4
3 4 7 8 3 4
3 4 7 8 3 4

Können Sie sich denken, warum? Das Problem hat mit dem Variablennamen zu tun, auf den später die Referenz verweist. Bei jedem Durchlauf der Schleife ändert sich zwar der Inhalt der Variablen @list, aber die Speicherposition bleibt die gleiche. Jedesmal, wenn Sie eine Zeile lesen, legen Sie im äußeren Array eine Referenz auf die gleiche Speicherposition ab. Das Array, das Sie am Ende erhalten, ist ein Array von vier Referenzen, die alle auf genau die gleiche Speicherposition zeigen.

Eine Möglichkeit, diesen Fehler zu beheben - und er tritt häufig auf, also hören Sie gut zu -, besteht darin, eine Referenz auf eine Kopie der Array-Daten und nicht auf das Array selbst zu erzeugen. Auf diese Weise legen Sie im Speicher bei jedem Durchlauf der Schleife eine neue Position im Speicher an und erhalten Referenzen auf verschiedene Speicherplätze. Man erreicht dies, indem man einfach die eckigen Klammern für anonyme Arrays um die Array-Variable setzt:

my @list = ();
while (<>) {
chomp;
@list = split;
push @matrix, [ @list ];
}

Achten Sie darauf, dass Sie für anonyme Hashes analog vorgehen (also anonyme Hashes in ein Array von Hashes ablegen):

push @arrayvonhashes, { %hash };

Das Problem läßt sich aber auch damit lösen, dass Sie in der Schleife eine my-Variable verwenden. Da die my-Variable bei jedem Schleifendurchlauf neu erzeugt wird, zeigt die Referenz jedesmal im Speicher woanders hin:

while (<>) {
chomp;
my @list = split;
push @matrix, \@list ;
}

Zugriff auf Elemente in verschachtelten Datenstrukturen

Verschachtelte Datenstrukturen zu erstellen ist eine Sache, auf die Elemente in verschachtelten Datenstrukturen zuzugreifen eine andere. Über Referenzen in Arrays, auf die wieder andere Referenzen weisen, auf ein bestimmtes Element zuzugreifen, ist schon eine unangenehme Aufgabe, besonders bei komplexen Strukturen. Doch zum Glück unterstützt Sie Perl dabei mit einer speziellen Syntax.

Angenommen Sie haben eine Matrix (Array von Arrays) von Zahlen, wie wir sie schon im obigen Abschnitt gesehen haben:

@zahlen = (
[ 3, 4, 2, 4, 2, 3 ],
[ 5, 3, 2, 4, 5, 4 ],
[ 7, 6, 3, 2, 8, 3 ],
[ 3, 4, 7, 8, 3, 4 ],
);

Nehmen wir jetzt an, Sie wollten auf das vierte Element in der dritten Reihe zugreifen. Mit Hilfe der Standardsyntax für den Arrayzugriff können Sie auf die dritte Reihe zugreifen:

$zahlen[2];

Das Ergebnis wäre jedoch nur eine Referenz und nicht die Daten, auf die die Referenz verweist (erinnern Sie sich, Sie erhalten die Daten, auf die eine Referenz weist, nur durch explizites Dereferenzieren). Um die Referenz zu dereferenzieren und ein echtes Element zu erhalten, könnten Sie folgendermaßen vorgehen:

$zahlen[2]->[3];  # liefert das vierte Element des Arrays, auf das die 
# Referenz $zahlen[2] verweist

oder so:

${ $zahlen[2] }[3];   # $zahlen[2] liefert Ihnen eine Referenz, die in dem 
# Block dereferenziert wird

Beide Varianten sind möglich, doch keine ist besonders gut lesbar. Deshalb stellt Perl Ihnen eine verkürzte Syntax für mehrdimensionale Arrays zur Verfügung, die den Zugriff vereinfacht: Wenn Sie die standardmäßige Dereferenzierungssyntax mit dem Pfeil verwenden, können Sie die ->-Zeichen fortlassen:

$zahlen[2][3];

Das ist wesentlich einfacher zu verstehen und entspricht dem Zugriff auf mehrdimensionalen Arrays in anderen Sprachen (wie C zum Beispiel).

Die Situation ist jedoch eine andere, wenn Sie anstatt eines echten Arrays in @zahlen nur eine Referenz auf ein Array von Arrays hätten. Dann wären nämlich zwei Referenzen zu dereferenzieren, und Sie würden folgende Syntax verwenden (hier ist @zahlenref die Referenz auf das Array von Arrays):

$zahlenref->[2][2];

Bei verschachtelten Hashes von Arrays und Hashes von Hashes verhält es sich analog, mit geschweiften Klammern für die Hash-Schlüssel und eckigen Klammern für die Array-Indizes:

$hash{joe}[5]; # Zugriff auf das sechste Element eines Arrays 
# des Hash %hash über den Schlüssel 'joe'
$hashref->{joe}[5]; # das gleiche, wenn $hashref eine Referenz enthält
$hash{Jones}{alter}; # Wert alter für den Jones-Datensatz in %hash
$hashref->{Jones}{alter}; # das gleiche, $hashref ist Referenz

Wenn Sie all diese verschachtelten Indizes und Schlüssel zu sehr verwirren, können Sie statt dessen von der Referenz auf das interne Array oder Hash, das Sie gerade interessiert, eine temporäre Kopie erzeugen und dann diese Referenz auf normalem Weg dereferenzieren:

my $tempref = $zahlen[0];  # Referenz auf die erste Reihe von Zahlen
print $$tempref[5]; # gibt fünftes Element aus
# das gleiche wie $zahlen[0][5]

Sie sind nur an einem Teilbereich eines verschachtelten Arrays interessiert? Normalerweise würden Sie dafür die übliche Syntax verwenden, mit den Referenzen an den entsprechenden Stellen. Des weiteren sind Sie in diesem Fall auf die Blockdereferenzierung angewiesen und am Ende erhalten Sie einen ziemlich häßlichen Ausdruck wie zum Beispiel:

@elemente = @{ $zahlen[1] }[2..5];  # extrahiert die Elemente 2 bis 5 
# im zweiten Array aus @zahlen):

Da diese Notation sehr schnell ziemlich unangenehm werden kann, ist es meist leichter, die Referenzen in temporären Variablen abzulegen und über diese die Teilbereiche zu entnehmen. Oder Sie setzen Schleifen auf, in denen die Elemente einzeln aus dem verschachtelten Array herausgezogen werden. Wenn Sie vertikale Bereiche herauslösen wollen (je ein Element aus verschiedenen der verschachtelten »Reihen«) oder rechteckige Bereiche (einige Elemente horizontal und mehrere Elemente vertikal), dann müssen Sie dazu eine Schleife verwenden.

Ein Beispiel: Eine Datenbank mit Künstlern und ihren Werken

Verschachtelte Datenstrukturen eignen sich bestens zum Verwahren komplexer Datensätze und ermöglichen es, diese Daten auf verschiedene Art und Weise zu manipulieren. In diesem Abschnitt untersuchen wir eine Datenbank, in der Künstler aufgeführt werden. Neben den Namen der Künstler enthält die Datenbank Informationen zu den einzelnen Künstlern und ihren Werken. Um Platz zu sparen, halte ich das Beispiel kurz und beschränke mich darauf,

Die Daten, die wir in diesem Beispiel betrachten, umfassen den Vor- und Nachnamen der Künstler, ihr Geburts- und Todesjahr sowie eine Liste der Titel ihrer Werke. Die Künstlerdaten sind in einer externen Datei gespeichert, die pro Künstler zwei Zeilen aufweist:

Monet,Claude,1840,1926
Herbst in Argenteuil:Pappeln:Camille:Wasserlilien

Die erste Zeile besteht aus den persönlichen Daten des Künstlers, die jeweils durch Kommata getrennt sind, die zweite Zeile enthält die Werke des Künstlers, getrennt durch Doppelpunkte. Die Datendatei, die ich kuenstler.txt genannt habe, enthält eine Reihe von Künstlern im gleichen Format.

Die Struktur, in die wir diese Informationen lesen, ist ein Hash von Hashes mit einem verschachtelten Array. Der oberste Hash verwendet als Schlüssel den Nachnamen des Künstlers. Die weiteren Künstlerdaten sind in einem verschachtelten Hash mit den Schlüsseln FN, BD, DD und works. Der Wert für den Schlüssel works ist wiederum ein Array, in dem die einzelnen Titel aufgeführt sind.

Listing 19.1 enthält den Code für dieses einfache Beispiel. Bevor Sie dazu übergehen, die Analyse dieses Codes zu lesen, studieren Sie die Zeilen in der while-Schleife, vor allem der Subroutine &read_input() (Zeilen 21 bis 35) und der Dereferenzierungen in der Subroutine &process() (Zeilen 53 und 55).

Listing 19.1: Das Skript kuenstler.pl

1:  #!/usr/bin/perl -w
2: use strict;
3:
4: my $artdb = "kuenstler.txt"; # Name der Künstlerdatenbank
5: my %artists = (); # Hash der Künstler, Nachname
# als Schlüssel
6:
7: &read_input();
8: &process();
9:
10: sub read_input {
11: my $in = ''; # temp. Eingabezeile
12: my ($fn,$ln,$bd,$dd); # Nachname, Vorname
13: # Geburtsjahr, Todesjahr
14: my %artist = (); # temp. Künstler-Hash
15:
16: open(FILE, $artdb) or
die "Datenbank ($artdb) konnte nicht geöffnet werden: $!\n";
17:
18: while () {
19: # Name und Daten in erster Zeile
20: chomp($in = <FILE>);
21: if ($in) {
22: ($ln,$fn,$bd,$dd) = split(',',$in);
23: $artist{FN} = $fn;
24: $artist{BD} = $bd;
25: $artist{DD} = $dd;
26:
27: chomp($in = <FILE>); # Liste der Werke in zweiter Zeile
28: if ($in) {
29: my @works = split(':',$in);
30: $artist{works} = \@works;
31: } else { print "no works";}
32:
33: # die Referenz auf das artist-Hash in das äußere
34: # artists-Hash eintragen
35: $artists{$ln} = { %artist };
36:
37: } else { last; } # Ende von DB
38: }
39:
40: }
41:
42: sub process {
43: my $input = '';
44: my $matched = 0;
45:
46: print "Geben Sie einen Künstlernamen ein: ";
47: chomp($input = <>);
48:
49: foreach (keys %artists) {
50: if (/$input/i and !$matched) {
51: $matched = 1;
52: my $ref = $artists{$_};
53: print "$_, $ref->{FN} $ref->{BD}-$ref->{DD}\n";
54: my $work = '';
55: foreach $work (@{$ref->{works}}) {
56: print " $work\n";
57: }
58: }
59: }
60: if (!$matched) {
61: print "Der Künstler $input wurde nicht gefunden.\n";
62: }
63: }

Vielleicht ist Ihnen aufgefallen, dass ich in diesem Beispiel genau das Gegenteil von dem gemacht habe, was ich zuvor gepredigt habe: Anstatt alle Variablen lokal zu halten, verwende ich eine globale Variable für die globale Künstlerdatenbank. Wie Sie Ihre Daten und Variablen organisieren, bleibt Ihnen überlassen. Ich verwende in diesem Fall eine globale Variable, weil das Dereferenzieren ohnehin schon kompliziert genug ist und ich daher nicht noch eine weitere Ebene hinzufügen möchte.

Was Ihnen in diesem Beispiel vielleicht auch noch seltsam erscheinen mag, ist der hartcodierte Name der Künstlerdatenbank. Ich habe den Namen der Datei bewußt in den Code des Skripts mit aufgenommen, statt ihn über die Befehlszeile eingeben zu lassen. Aber auch dies ist eine Frage des Programmierstils und wie das Skript verwendet wird. Beide Wege sind gangbar (beachten Sie, dass ich den Dateinamen der Künstlerdatenbank ganz oben im Skript gesetzt habe, so dass er bei Bedarf schnell geändert werden kann).

Betrachten wir zuerst die Subroutine &read_input(), die die Künstlerdatenbank einliest und unsere verschachtelte Datenstruktur mit Daten füllt. Ich bin dabei so vorgegangen, dass ich einen temporären Hash für den aktuellen Künstler einrichte, diesen Hash mit Daten fülle und dann den temporären Hash über eine Referenz in den äußeren Hash eintrage.

Wir beginnen in Zeile 18 mit einer Schleife, die die Datenbankdatei der Künstler in Schritten von je zwei Zeilen einliest. Die Schleife wird verlassen, wenn es keine weiteren Daten mehr gibt (Test in Zeile 21). Zuerst wird die erste Zeile der Daten eingelesen, die den Namen des Künstlers sowie seine persönlichen Daten enthält:

Monet,Claude,1840,1926

Zeile 22 zerlegt diese Daten in ihre Einzelbestandteile, und die Zeilen 23 und 25 legen diese Daten dann in einem temporären Hash ab (namens %artist, der jedoch nicht mit dem äußeren Hash %artists verwechselt werden sollte).

Zeile 27 liest die jeweils zweite Zeile der Künstlerdaten ein, das heißt die einzelnen Werke:

Herbst in Argenteuil:Pappeln:Camille:Wasserlilien

In Zeile 29 teilen wir diese Zeile auf der Basis des Trennzeichens »:« in Listenelemente auf und speichern diese Liste in dem temporären Array @works. In Zeile 30 fügen wir eine Referenz auf dieses Array in unseren temporären Hash %artist mit dem Schlüssel »works« ein. Beachten Sie, dass wir bei jedem Durchlauf der while-Schleife einen neuen temporären @works-Array (deklariert mit my) erhalten, so dass wir das Problem, jedesmal die gleiche Speicherposition zu referenzieren, umgehen.

Nachdem wir auf diese Weise die Daten des Künstlers eingelesen haben, können wir diesen Datensatz endlich in den äußeren Künstler-Hash eintragen, wobei uns der Nachname des Künstlers als Schlüssel dient. Und genau das geschieht in Zeile 35. Beachten Sie hierbei, dass wir, da bei jedem Schleifendurchlauf der gleiche Hash %artist verwendet wird, einen anonymen Hash-Konstruktor und eine Kopie des Hash %artist verwenden, um sicherzustellen, dass die Referenz jedesmal auf eine andere Speicherposition weist.

Die Subroutine &read_input() legt die Daten in einem verschachtelten Hash ab, die Subroutine &process() holt die Daten wieder heraus. Dazu bedienen wir uns eines einfachen Algorithmus, um nach dem Nachnamen des Künstlers zu suchen und den gefundenen Datensatz auszugeben. Die Ausgabe dieser Subroutine sieht wie folgt aus:

Geben Sie einen Künstlernamen ein: Monet
Monet, Claude 1840-1926
Herbst in Argenteuil
Pappeln
Camille
Wasserlilien

Den wichtigsten Teil dieser Subroutine bilden die Zeilen 52 bis 56, in denen die Referenzen dereferenziert werden, um an die benötigten Daten zu gelangen. Lassen Sie uns aber weiter oben in Zeile 49 mit der foreach-Schleife beginnen. Da wir keine eigentliche Schleifenvariable haben, speichert Perl die Schlüssel (die Nachnamen der jeweiligen Künstler) in der Variablen $_.

Zeile 50 enthält den eigentlichen Test: Wir führen mit der Eingabe und dem aktuellen Schlüssel einen Mustervergleich durch, um festzustellen, ob es eine Übereinstimmung gibt. Und da wir in diesem Beispiel nur an der ersten Übereinstimmung interessiert sind, verfolgen wir außerdem die Variable $matched, um zu sehen, ob bereits eine Übereinstimmung gefunden wurde.

Angenommen es wurde ein übereinstimmender Datensatz gefunden, dann gehen wir weiter zu Zeile 52. Hier erzeugen wir eine temporäre Variable, die die Referenz auf den Datensatz des Künstlers aufnimmt - dies ist, wie in unserem stats-Beispiel, nicht unbedingt notwendig, erleichtert aber die Verwaltung der Referenzen. Da $_ den übereinstimmenden Schlüssel enthält, können wir die Referenz in diesem Falle mit einer einfachen Hash-Suche ermitteln.

Haben wir erst einmal die Referenz, können wir sie dereferenzieren, um Zugriff auf den Inhalt des Hash zu erhalten. In Zeile 53 geben wir die persönlichen Daten aus: den Nachnamen ($_), den Vornamen (der Wert des Schlüssels FN im Hash), das Geburtsjahr (BD) und das Todesjahr (DD).

Die Zeilen 54 bis 56 dienen dazu, die Werke des Künstlers auf jeweils einer Zeile auszugeben. Merkwürdig ist allein die Referenz in der foreach-Schleife. Betrachten wir diese einmal näher:

@{$ref->{works}}

Zur Erinnerung: In $ref befindet sich eine Referenz auf einen Hash. Der Ausdruck $ref->{works} dereferenziert diese Referenz und liefert den Wert zurück, der durch den Schlüssel works gegeben ist. Dieser Wert ist wiederum eine Referenz, diesmal jedoch eine Referenz auf ein Array. Um diese Referenz zu dereferenzieren und ein tatsächliches Array zu erhalten, das man mit der foreach-Schleife durchlaufen kann, bedarf es der Blocksyntax zum Dereferenzieren: @{}.

Referenzen zu verstehen und herauszufinden, wie man an die tatsächlichen Daten herankommt, ist nicht immer ganz einfach. Meist hilft es, wenn man von außen nach innen vorgeht, wo nötig, Blöcke verwendet und wenn hilfreich, temporäre Variablen einsetzt. Auch die Analyse der Referenzausdrücke im Perl-Debugger oder mit print- Anweisungen kann helfen, die richtigen Dereferenzierungen zu erzeugen.

Vertiefung

Die Erzeugung und Verwendung von Referenzen ist sicherlich einer der komplexeren Aspekte von Perl (vermutlich nur von der objektorientierten Programmierung übertroffen, die wir morgen besprechen wollen). Die heutige Lektion hat Sie in die Grundlagen der Referenzen eingeführt und Ihnen die wichtigsten Einsatzbereiche aufgezeigt. Wie aber bei den meisten Themen in Perl gibt es auch im Zusammenhang mit Referenzen viele Bereiche, die ich nicht angesprochen habe, einschließlich der symbolischen Referenzen (eine gänzlich andere Form von Referenz) sowie der Referenzen auf Subroutinen, Typeglobs und Datei-Handles.

Weitere Informationen zu Referenzen finden Sie in der perlref-Manpage. Wenn Sie intensiver mit verschachtelten Datenstrukturen arbeiten, finden Sie weitere Details und Beispiele in perldsc (Kochbuch der Datenstrukturen) und perllol (Liste der Listen).

Kurzformen für Referenzen auf Skalare

Müssen Sie mehrere skalare Referenzen auf einmal erstellen? So geht es ganz leicht:

@listevonrefs = \($ding1, $ding2, $ding3, $ding4);

Auf diese Weise erhalten Sie in @listevonrefs eine Liste von Referenzen. Es ist eine Kurzform von :

@listevonrefs = (\$ding1, \$ding2, \$ding3, \$ding4);

Symbolische Referenzen

Wie ich zu Beginn dieser Lektion schon am Rande bemerkt habe, kennt Perl eigentlich zwei Arten von Referenzen: harte Referenzen und symbolische Referenzen. Die von mir in dieser Lektion durchgehend verwendeten Referenzen waren harte Referenzen. Harte Referenzen sind eigentlich skalare Daten, die wie Skalare manipuliert werden können und die dereferenziert werden, um auf die referenzierten Daten zuzugreifen.

Symbolische Referenzen sind anders: Eine symbolische Referenz ist einfach ein String. Wenn Sie versuchen, diesen String zu dereferenzieren, wird der String als der Name einer Variablen interpretiert, und falls diese Variable existiert, erhalten Sie den Wert dieser Variablen. Sehen Sie dazu folgendes Beispiel:

$foo = 1;                                   # Variable $foo enthält Wert 1
$symref = "foo"; # String
$$symref = "Ich bin eine Variable"; # setzt die Variable $foo
print "symbolische Referenz: $symref\n"; # Ergebnis ist "foo"
print "Foo: $foo\n"; # Ergebnis ist "Ich bin eine Variable"
print "dereferenziert: $$symref\n"; # gibt $foo aus,
# Ergebnis ist "Ich bin eine Variable"

Wie Sie sehen können, lassen sich symbolische Referenzen wie richtige Referenzen verwenden, es sind jedoch nur Strings, die Variablen benennen. Der Unterschied ist sehr fein und verwirrend, besonders wenn Sie harte und symbolische Referenzen kombinieren. So könnten Sie aus Versehen einen String dereferenzieren, wenn Sie eigentlich vorhatten, einen Skalar zu dereferenzieren, ein Fehler, der sich nur schwer beim Debuggen aufdekken läßt. Aus diesem Grund verfügt Perl über ein strict- Pragma, mit dem Sie die Verwendung von Referenzen auf harte Referenzen beschränken können.

use strict 'refs';

Wenn Sie dieses Pragma ganz oben in Ihr Skript mit aufnehmen, können Sie verhindern, dass symbolische Referenzen verwendet werden. Den gleichen Effekt erzielen Sie aber auch, wenn Sie oben in Ihrem Skript use strict allein verwenden.

Referenzen auf Typeglobs und Datei-Handles

Zwei Arten von Referenzen, die ich im Hauptteil dieser Lektion nicht besprochen habe, sind die Referenzen auf Typeglobs, die wiederum Referenzen auf Datei-Handles ermöglichen. Ein Typeglob ist, wie ich weiter vorn in diesem Buch kurz bemerkt habe, eine Möglichkeit, auf mehrere Typen von Variablen, die den gleichen Namen teilen (der in der Symboltabelle eingetragen ist), gleichzeitig Bezug zu nehmen. Typeglobs werden heutzutage in Perl nicht mehr so oft verwendet wie früher, als es noch keine Referenzen gab und man die Typeglobs dazu nutzte, Verweise auf Listen an Subroutinen zu übergeben. Typeglobs stellen aber auch einen Weg dar, um Referenzen auf Datei-Handles zu erzeugen, und geben uns damit eine Möglichkeit an die Hand, bei Bedarf Datei-Handles in und aus Subroutinen zu übergeben oder lokale Datei-Handles zu erzeugen.

Um eine Referenz auf einen Datei-Handle zu erzeugen, verwenden Sie einen Typeglob mit dem Namen des Datei-Handles und dem Backslash-Operator \:

$fh = \*MEINEDATEI;

Für den lokalen Datei-Handle verwenden Sie den Operator local (nicht my) und ein Datei-Handle-Typeglob:

local *MEINEDATEI;

Weitere Informationen finden Sie in der perldata-Manpage (im Abschnitt zu Typeglobs und Datei-Handles).

Referenzen auf Subroutinen

Noch nützlicher als Referenzen auf Datei-Handles sind Referenzen auf Subroutinen. Da die Definitionen von Subroutinen wie Arrays oder Hashes im Speicher abgelegt sind, können Sie - wie bei anderen Daten auch - Referenzen auf Subroutinen erzeugen. Wenn Sie eine Referenz auf eine Subroutine dereferenzieren, rufen Sie die Subroutine auf.

Mit Referenzen auf Subroutinen können Sie die Definition einer Subroutine während der Ausführung ändern oder je nach Situation zwischen verschiedenen Subroutinen wählen. Referenzen auf Subroutinen öffnen zudem das Tor zu fortgeschrittenen Techniken in Perl, wie zum Beispiel die objektorientierte Programmierung und Closures (anonyme Subroutinen, deren lokale Variablen sich danach richten, in welchem Gültigkeitsbereich die Subroutine definiert wurde - auch wenn sie später in einem anderen Gültigkeitsbereich aufgerufen wurden).

Um eine Referenz auf eine Subroutine zu erzeugen, verwenden Sie den Backslash- Operator mit dem Namen einer bereits definierten Subroutine:

$subref = \&meinesub;

Sie können auch Referenzen auf anonyme Subroutinen erzeugen, indem Sie einfach den Namen der Subroutine bei der Definition fortlassen:

$subref = sub { reverse @_;};

Zum Dereferenzieren verwenden Sie die bekannte Referenzsyntax oder einen Block. Wenn Sie eine Subroutine dereferenzieren, rufen Sie sie auf; deshalb sollten Sie nicht vergessen, die Argumente mit einzuschließen:

@ergebnis = &$subref(1..10);

Weitere Details zu Referenzen auf Subroutinen und zu Closures finden Sie in der perlref-Manpage.

Zusammenfassung

Der letzte große Teilbereich von Perl, der in diesem Buch noch zur Besprechung anstand, betraf die Referenzen. Mit der heutigen Lektion haben Sie eine gute Einführung in die Erzeugung und Verwendung von Referenzen in verschiedenen Kontexten erhalten.

Referenzen sind so etwas wie skalare Daten, die auf andere Daten (einen anderen Skalar, ein Array, einen Hash oder eine Subroutine) verweisen. Da Referenzen Skalare sind, können Sie sie an Subroutinen übergeben, in Variablen speichern, sie als String oder als Zahl behandeln, auf ihren Wahrheitswert testen oder sie in ein Array aufnehmen. Sie können Referenzen auf zwei Arten erzeugen:

Um Zugriff auf das zu erhalten, worauf die Referenz verweist, müssen Sie die Referenz dereferenzieren. Eine Referenz läßt sich auf drei Arten dereferenzieren:

Neben den Grundtechniken zur Erzeugung und Verwendung von Referenzen habe ich Ihnen zwei der häufigsten Einsatzbereiche für Referenzen vorgestellt: als Argumente für Subroutinen (um die Struktur von Arrays und Hashes bei der Übergabe an Subroutinen zu erhalten) und zur Erzeugung von verschachtelten Datenstrukturen wie Arrays von Arrays und Arrays von Hashes. Schließlich haben Sie die ref-Funktion kennengelernt, die einen String zurückliefert, der Ihnen anzeigt, welche Art Daten die Referenz enthält.

Meinen Glückwunsch! Heute haben Sie die wirklich harte Arbeit mit diesem Buch abgeschlossen. Morgen untersuchen wir noch einige weitere Perl-Konzepte, die bisher noch nicht zur Sprache gekommen sind, und in Kapitel 21 beenden wir das Buch mit einigen Beispielen, die das bisher Gelernte praktisch umsetzen.

Fragen und Antworten

Frage:
Kann man Referenzen auf Referenzen erzeugen?

Antwort:
Aber selbstverständlich! Alles, was Sie dazu tun müssen, ist den Backslash- Operator zu verwenden, um die Speicherposition der Referenz zu erhalten. Denken Sie jedoch daran, dass Sie Referenzen auf Referenzen zweimal dereferenzieren müssen, um an die eigentlichen Daten zu gelangen.

Frage:
Ich versuche ein Array von Arrays mit Daten aus einer Datei zu füllen. Ich lese die Daten in ein einfaches Array und füge dieses Array dann in ein äußeres Array ein. Aber am Ende enthält das ganze Array nichts außer den zuletzt hinzugefügten Werten. Was mache ich falsch?

Antwort:
Das klingt, als wenn Sie ungefähr folgendes versuchen:

while (<>) {
@input = split $_;
@grossesarray = \@input;
}

Frage:
Ich habe ein Array von Arrays erzeugt und mit der Anweisung print "@meinarrary\n"; ausgegeben. Aber erhalten habe ich nur:

ARRAY(0x807f048) ARRAY(0x808a06c) ARRAY(0x808a0cc)

Antwort:
Bei Arrays von Arrays ist eine Variableninterpolation nicht möglich. Ihr print- Befehl gibt nur die obere Ebene des Arrays aus - was im wesentlichen drei Referenzen sind. Die ARRAY(...)-Formulierungen sind lediglich die druckbaren Stringdarstellungen dieser Referenzen. Um ein verschachteltes Array (oder eine beliebige andere verschachtelte Datenstruktur) auszugeben, müssen Sie eine oder mehrere foreach-Schleifen verwenden und die Referenzen selbst dereferenzieren. Hier ein Beispiel dafür, wie sich dies realisieren ließe:

foreach (@meinarray) {
print "( @$_ )\n";
}

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.

Quiz

  1. Was sind Referenzen? Welche Vorteile bieten sie Ihnen?
  2. Beschreiben Sie zwei Wege, wie man eine Referenz auf ein Array erzeugt.
  3. Beschreiben Sie zwei Methoden, wie man über eine Referenz Zugriff auf ein Element in einem Array erhält.
  4. Was passiert mit einer Referenz, wenn Sie die Daten, auf die die Referenz weist, ändern?
  5. Was passiert, wenn Sie eine Referenz ausgeben? Was passiert, wenn Sie den Wert 4 zu der Referenz addieren? Was passiert, wenn Sie testen, ob die Referenz wahr ist.
  6. Angenommen Sie haben in $ref eine Referenz, die entweder auf einen Skalar, ein Array, einen Hash oder eine verschachtelte Datenstruktur verweist. Was ist das Ergebnis der folgenden Dereferenzierungen? (Gehen Sie davon aus, dass die Referenz auf die für jedes Beispiel geeignetsten Daten zeigt.)
        $$ref;
      $$ref[0];
      $ref->[0];
      @$ref;
      $#$ref;
      $ref->[0][5];
      @{$ref->{key}};

Übungen

  1. Erzeugen Sie eine Subroutine, die eine beliebige Zahl an Referenzen auf Arrays übernimmt, diese Arrays umkehrt und sie dann in der gleichen Reihenfolge, in der sie übernommen wurden, wieder zurückliefert.
  2. FEHLERSUCHE: Was ist falsch an folgendem Codefragment? (HINWEIS: Es gibt mehr als einen Fehler).
        %hash = {
    key => [ 1.. 10 ],
    key2 => [ 100 ..110],
    };
    $ref = \%hash;
    foreach (keys %$ref) {
    print "$$ref{$_}\n";
    }
  3. Schreiben Sie eine Subroutine, die einen rechteckigen Bereich aus einem mehrdimensionalen Array übernimmt. Ihre Subroutine sollte fünf Argumente übernehmen: eine Referenz auf das Array, aus dem der Bereich stammt, die Indizes des ersten Elements (Reihe und Element) und die Anzahl der Reihen und Elemente, die ausgeschnitten werden sollen. Wenn Sie also zum Beispiel eine Referenz auf ein mehrdimensionales Array haben, das folgendermaßen aussieht und in der Variablen $listref gespeichert ist:
        [ 3, 4, 2, 4, 2, 3 ]
    [ 5, 3, 2, 4, 5, 4 ]
    [ 7, 6, 3, 2, 8, 3 ]
    [ 3, 4, 7, 8, 3, 4 ]
  4. Und wenn Sie Ihre Subroutine (&rect()) mit dem Startpunkt 0,0 aufrufen, um ein Rechteck von 3x3 Elementen auszuschneiden
        &rect($listref,0,0,3,3);
  5. sollte Ihr Ergebnis folgendes sein:
        3 4 2
    5 3 2
    7 6 3
  6. Schreiben Sie eine einfache Version des Spiels »Schiffe versenken«. Verwenden Sie ein 5x5-Raster (Zahlen für die Reihen, Buchstaben für die Spalten), wählen Sie eine willkürliche Zelle, und erlauben Sie dann dem Benutzer, eine Zelle zu beschießen. Geben Sie nach jedem Schuß den aktuellen Stand des Bretts mit den eingezeichneten Versuchen aus. Hier ein Beispiel:
        Geben Sie die Koordination Ihrer Wahl ein (z.B. A4): C4
    Daneben! Neuer Versuch.

    A B C D E
    ---------
    1| 0 0 0 0 0
    2| 0 0 0 0 0
    3| 0 0 0 0 0
    4| 0 0 X 0 0
    5| 0 0 0 0 0

    Geben Sie die Koordination Ihrer Wahl ein (z.B. A4): B3
    Daneben! Neuer Versuch.

    A B C D E
    ---------
    1| 0 0 0 0 0
    2| 0 0 0 0 0
    3| 0 X 0 0 0
    4| 0 0 X 0 0
    5| 0 0 0 0 0

    Geben Sie die Koordination Ihrer Wahl ein (z.B. A4): E1
    Glückwunsch! Sie haben das Schiff versenkt!

Antworten

Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.

Antworten zum Quiz

  1. Referenzen sind skalare Daten, die es Ihnen erlauben, auf indirektem Weg auf andere Daten zu verweisen. Mit Referenzen können Sie Subroutinenargumente als Referenz übergeben (erhält die Struktur von mehrdimensionalen Arrays und Hashes), mehrere diskrete Listen zurückgeben, verschachtelte Datenstrukturen wie Listen von Listen erzeugen und verwalten.
  2. Referenzen auf Arrays kann man mit dem Backslash-Operator
        $ref = \@array;
  3. oder mit einem anonymen Array-Konstruktor erzeugen:
        $ref = [ 1 ..100 ];
  4. Um eine Referenz auf ein Array zu dereferenzieren und auf dessen Elemente zuzugreifen, setzen Sie die Referenz dort ein, wo sonst der Arrayname stehen würde:
        $ding = $$ref[0];
  5. oder verwenden Sie die Pfeil-Notation:
        $ding = $ref->[0];
  6. Änderungen an den Daten, auf die eine Referenz verweist, haben keine Auswirkungen auf die Referenz selbst. Die Referenz zeigt auf die Position der Daten im Speicher und nicht auf die Daten selbst.
  7. Eine Referenz ist ein Skalar und verhält sich wie ein Skalar in einem skalaren Kontext nach folgenden Regeln:
  1. Die korrekten Antworten lauten:
  2. a. In der Annahme, dass $ref eine Referenz auf einen Skalar ist, liefert der Ausdruck einen Skalar zurück.
  3. b. In der Annahme, dass $ref eine Referenz auf ein Array ist, liefert der Ausdruck das erste Element dieses Arrays zurück.
  4. c. Das gleiche wie b.
  5. d. In der Annahme, dass $ref eine Referenz auf ein Array ist, liefert der Ausdruck den Inhalt des Arrays (oder in einem skalaren Kontext die Anzahl der Elemente) zurück.
  6. e. In der Annahme, dass $ref eine Referenz auf ein Array ist, liefert der Ausdruck den letzten Index des Arrays zurück.
  7. f. In der Annahme, dass $ref eine Referenz auf ein mehrdimensionales Array ist, liefert der Ausdruck das sechste Element in der ersten Reihe zurück.
  8. g. In der Annahme, dass $ref eine Referenz auf einen Hash von Arrays ist, liefert der Ausdruck den Inhalt des Arrays zurück, das den Schlüssels key hat.

Antworten zu den Übungen

  1. Hier ist eine Antwort:
        sub reverseall {
    my $listref;
    foreach $listref (@_) {
    if (ref($listref) eq 'ARRAY') {
    my @templist = reverse @$listref;
    $listref = \@templist;
    } else {
    print "$listref ist keine Liste\n";
    }
    }
    return @_;
    }
  2. Es gibt zwei Fehler in diesem Fragment. Der erste befindet sich in der Definition des Hash. Die geschweiften Klammern um die Hash-Definition ({}) erzeugen eine Referenz auf einen Hash und keinen normalen Hash. Dafür müssen Sie wie folgt runde Klammern verwenden:
        %hash = (
    key => [ 1.. 10 ],
    key2 => [ 100 ..110],
    );
  3. Der zweite Fehler befindet sich in der print-Anweisung. Diese Anweisung dereferenziert erfolgreich $ref und gibt die Werte im Hash aus. Da die Werte im Hash aber wiederum Referenzen sind, wird die Ausgabe folgendermaßen aussehen:
        ARRAY(0x807f670)
    ARRAY(0x807f7a8)
  4. Um die eigentlichen Werte dieser Arrays auszugeben, müssen Sie die Referenzen ebenfalls dereferenzieren. Sie können dies durch komplizierte Dereferenzierungen:
        print "@{ $$ref{$_} }\n";
  5. oder durch die Verwendung von temporären Referenzen (die die Lesbarkeit erhöhen) erreichen:
        my $arrayref = $$ref{$_};
    print "@$arrayref\n";
  6. Hier ist eine Antwort (die jedoch keine Fehlermeldungen ausgibt, wenn Bereiche ausgewählt werden, die breiter oder höher als die zugrundeliegenden Daten sind; die Subroutine schneidet in so einem Fall so viele Elemente wie möglich heraus):
        sub rect {
    my $ref = shift;
    my ($c1,$c2,$width,$height) = @_;
    my @finalarray = ();
    my @slice = ();
    my $rowref;

    for (; $height > 0; $height--) { # Reihen
    my $c = $c2;
    if ($$ref[$c1]) { # fängt zu große Höhen ab
    $rowref = $$ref[$c1];
    } else {next;}

    for (my $w = $width; $w > 0; $w--) { # Spalten
    if ($$rowref[$c]) { # fängt zu breite Breiten ab
    push @slice, $$rowref[$c];
    $c++;
    }
    }
    push @finalarray, [ @slice ];
    @slice = (); # setzt den Bereich für das nächste Mal zurück
    $c1++;
    }
    return \@finalarray;
    }
  7. Hier ist ein Beispiel. Das »Brett« ist ein verschachteltes Array von Arrays:
        #!/usr/bin/perl -w
    use strict;

    my @board = (
    [ 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0 ],
    [ 0, 0, 0, 0, 0 ],
    );
    my $hit = 0;
    my @coords = &init();

    while () {
    &print_board();
    my @choice = &get_coords();

    if (&compare_coords(@choice,@coords)) {
    print "Glückwunsch! Sie haben das Schiff versenkt!\n";
    last;
    } else {
    print "Daneben! Neuer Versuch.\n";
    &mark_board(@choice);
    }
    }

    sub init {
    srand;
    my $num1 = int(rand 5);
    my $num2 = int(rand 5);
    return ($num1, $num2);
    }

    sub print_board {
    $, = ' ';
    print "\n A B C D E \n";
    print " --------- \n";
    my $i = 1;
    foreach (@board) {
    print "$i| @$_ \n";
    $i++;
    }
    print "\n";
    }

    sub get_coords {
    my ($c1, $c2);
    my $coords;
    while () {
    print "Geben Sie die Koordinaten Ihrer Wahl ein (z.B. A4): ";
    chomp($coords = <>);
    ($c1,$c2) = split('',$coords);
    $c1 = uc $c1;

    if ($c1 !~ /[ABCDE]/i) {
    print "Ungültige Buchstaben-Koordinate. A - E, bitte.\n";
    next;
    } elsif ($c2 !~ /[1-5]/) {
    print "Ungültige Zahlen-Koordinate. 1- 5 bitte.\n";
    next;
    } else { last; }
    }
    ($c1 eq 'A') and $c1 = 0;
    ($c1 eq 'B') and $c1 = 1;
    ($c1 eq 'C') and $c1 = 2;
    ($c1 eq 'D') and $c1 = 3;
    ($c1 eq 'E') and $c1 = 4;
    $c2--;

    return ($c1, $c2);
    }


    sub compare_coords {
    my ($c1,$c2,$d1,$d2) = @_;
    if ($c1 == $d1 and $c2 == $d2) { return 1; }
    else { return 0; }
    }

    sub mark_board {
    my ($c2,$c1) = @_;
    $board[$c1][$c2] = 'X';
    }


vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH